package net.toby200.canalstoppages;

import com.fasterxml.jackson.databind.ObjectMapper;
import com.fasterxml.jackson.databind.SerializationFeature;
import com.googlecode.objectify.Key;
import com.googlecode.objectify.LoadResult;
import com.googlecode.objectify.ObjectifyService;

import net.toby200.canalstoppages.model.Notice;
import net.toby200.canalstoppages.model.ParentEntity;
import net.toby200.canalstoppages.notify.Notifier;
import net.toby200.canalstoppages.scraper.INoticeListScraper;
import net.toby200.canalstoppages.scraper.JSoupNoticeListScraper;

import org.apache.commons.lang.StringUtils;

import java.io.IOException;
import java.util.Collection;
import java.util.HashSet;
import java.util.List;
import java.util.logging.Level;
import java.util.logging.Logger;

import javax.servlet.http.HttpServlet;
import javax.servlet.http.HttpServletRequest;
import javax.servlet.http.HttpServletResponse;

import static com.googlecode.objectify.ObjectifyService.ofy;

@SuppressWarnings("serial")
public class CanalStoppagesCron extends HttpServlet {
	
	private static final Logger LOG = Logger.getLogger(CanalStoppagesCron.class.getName());
    private final ObjectMapper m = new ObjectMapper();

    static {
		ObjectifyService.register(Notice.class);
	}
	
	public void doGet(HttpServletRequest req, HttpServletResponse resp)
			throws IOException {
		
		try {
			LOG.log(Level.INFO, "Running cron");
	
		
			INoticeListScraper scraper = new JSoupNoticeListScraper();
			final List<Notice> currentNotices = scraper.getNotices(Config.NOTICES_URL);

			final Collection<Key<Notice>> currentNoticeKeys = new HashSet<Key<Notice>>();
            final Collection<Notice> updatedNotices  = getUpdatedNotices(currentNotices, currentNoticeKeys);

            // persist the collection rather than individual items to avoid issues with persisting too many updates a second
            ofy().save().entities(updatedNotices);

            Notifier.sendNotification(updatedNotices);
			
			// remove any notices in cache that aren't on website still
			// if we got no current notices at all, assume there's an issue and don't delete anything
			final List<Key<Notice>> keysToDelete = ofy().load().type(Notice.class).keys().list();
			keysToDelete.removeAll(currentNoticeKeys); // don't remove any keys from the current list
			if (currentNotices.size() > 0 && keysToDelete.size() > 0) {
				ofy().delete().keys(keysToDelete).now();
				LOG.info("Deleted " + keysToDelete.size() + " old entries");
			}
		
			// this is unnecessary just provides output for easy verification
			m.configure(SerializationFeature.FAIL_ON_EMPTY_BEANS, false);
			m.enable(SerializationFeature.INDENT_OUTPUT);
			String json = m.writeValueAsString(currentNotices);
			resp.setContentType("text/plain");
			resp.getWriter().println(json);
			
			LOG.log(Level.FINE, json);
			LOG.log(Level.INFO, "Notices loaded: " + currentNotices.size() );
			
		} catch (Exception e) {
			LOG.log(Level.SEVERE, "Error running cron job", e);
		}

	}

    private Collection<Notice> getUpdatedNotices(List<Notice> currentNotices, Collection<Key<Notice>> newNoticeKeys) {
        Collection<Notice> updatedNotices = new HashSet<Notice>();

        for (Notice notice : currentNotices) {
            Key<Notice> key = Key.create(ParentEntity.getKey(), Notice.class, notice.getId());
            newNoticeKeys.add(key);

            // check if we have the notice in the cache
            LoadResult<Notice> oldNoticeLoader = ofy().load().key(key);
            Notice oldNotice = oldNoticeLoader != null ? oldNoticeLoader.now() : null;

            if (!notice.equals(oldNotice)) {
                if (oldNotice == null) {
                    LOG.info("New notice: " + notice.getId());
                } else {
                    LOG.log(Level.INFO, "Notice changed: " + notice);
                    LOG.log(Level.INFO, "Notice diff: " +
                            StringUtils.difference(notice.toString(), oldNotice.toString()));
                }
                updatedNotices.add(notice);

            } // else no change since last time
        }
        LOG.info("Number of updated notices: " + updatedNotices.size());
        return updatedNotices;
    }
}
